/**
 * 
 */
package org.type4neo.imp;

import java.util.Iterator;

import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.ReturnableEvaluator;
import org.neo4j.graphdb.StopEvaluator;
import org.neo4j.graphdb.Traverser;
import org.neo4j.graphdb.Traverser.Order;
import org.type4neo.TypedNode;
import org.type4neo.TypedRelationship;
import org.type4neo.bytecode.DynamicTyper;

/**
 * @author pragone
 *
 */
public abstract class TypedNodeImp extends TypedPrimitiveImp implements TypedNode {
	
	public TypedNodeImp() {
	}
	
	public Node getUnderlyingNode() {
		return (Node) this.pc;
	}

	public Node mutateTo(Class<? extends TypedNode> newType) {
		if (newType == null || !newType.isInterface()) throw new IllegalArgumentException("Given new type must be an Instance class");
		this.pc.setProperty(DynamicTyper.PROTOTYPE_PROPERTY_NAME, newType.getName());
		return DynamicTyper.wrapNode((Node) this.pc);
	}

	public long getId() {
		return ((Node) this.pc).getId();
	}

	public void delete() {
		((Node) this.pc).delete();
	}

	public Iterable<Relationship> getRelationships() {
		return new Iterable<Relationship>() {
			
			public Iterator<Relationship> iterator() {
				return new RelationshipWrapperIterator(((Node) pc).getRelationships().iterator());
			}
		};
	}

	public boolean hasRelationship() {
		return ((Node) this.pc).hasRelationship();
	}

	@Deprecated
	public Iterable<Relationship> getRelationships(RelationshipType... types) {
		final Iterable<Relationship> temp = ((Node) pc).getRelationships(types);
		return new Iterable<Relationship>() {
			
			public Iterator<Relationship> iterator() {
				return new RelationshipWrapperIterator(temp.iterator());
			}
		};
	}

	public Iterable<TypedRelationship> getRelationships(Class<? extends TypedRelationship>... types) {
		if (types == null || types.length == 0) return null;
		RelationshipType[] temp = new RelationshipType[types.length];
		for (int i = 0; i < types.length; i++) {
			temp[i] = DynamicRelType.getType(types[i]);
		}
		final Iterable<Relationship> temp2 = ((Node) pc).getRelationships(temp);
		return new Iterable<TypedRelationship>() {
			
			public Iterator<TypedRelationship> iterator() {
				return new TypedRelationshipWrapperIterator(temp2.iterator());
			}
		};
	}

	public boolean hasRelationship(RelationshipType... types) {
		return ((Node) this.pc).hasRelationship(types);
	}

	public boolean hasRelationship(Class<?>... types) {
		if (types == null || types.length == 0) return false;
		RelationshipType[] temp = new RelationshipType[types.length];
		for (int i = 0; i < types.length; i++) {
			temp[i] = DynamicRelType.getType(types[i]);
		}
		return hasRelationship(temp);
	}

	public Iterable<Relationship> getRelationships(Direction dir) {
		final Iterable<Relationship> temp = ((Node) pc).getRelationships(dir);
		return new Iterable<Relationship>() {
			
			public Iterator<Relationship> iterator() {
				return new RelationshipWrapperIterator(temp.iterator());
			}
		};
	}

	public boolean hasRelationship(Direction dir) {
		return ((Node) pc).hasRelationship(dir);
	}

	@Deprecated
	public Iterable<Relationship> getRelationships(RelationshipType type,
			Direction dir) {
		final Iterable<Relationship> temp = ((Node) pc).getRelationships(type, dir);
		return new Iterable<Relationship>() {
			
			public Iterator<Relationship> iterator() {
				return new RelationshipWrapperIterator(temp.iterator());
			}
		};
	}

	public Iterable<TypedRelationship> getRelationships(Class<? extends TypedRelationship> type,
			Direction dir) {
		final Iterable<Relationship> temp = ((Node) pc).getRelationships(DynamicRelType.getType(type), dir);
		return new Iterable<TypedRelationship>() {
			
			public Iterator<TypedRelationship> iterator() {
				return new TypedRelationshipWrapperIterator(temp.iterator());
			}
		};
	}

	@Deprecated
	public boolean hasRelationship(RelationshipType type, Direction dir) {
		return ((Node) pc).hasRelationship(type, dir);
	}

	public boolean hasRelationship(Class<? extends TypedRelationship> type, Direction dir) {
		return ((Node) pc).hasRelationship(DynamicRelType.getType(type), dir);
	}

	@Deprecated
	public Relationship getSingleRelationship(RelationshipType type,
			Direction dir) {
		return ((Node) pc).getSingleRelationship(type, dir);
	}

	public TypedRelationship getSingleRelationship(Class<? extends TypedRelationship> type,
			Direction dir) {
		return DynamicTyper.wrapRelationship(((Node) pc).getSingleRelationship(DynamicRelType.getType(type), dir));
	}

	@Deprecated
	public Relationship createRelationshipTo(Node otherNode,
			RelationshipType type) {
		return ((Node) this.pc).createRelationshipTo(otherNode, type);
	}
	
	public TypedRelationship createRelationshipTo(Node otherNode, Class<? extends TypedRelationship> type) {
		Relationship rel = ((Node) this.pc).createRelationshipTo(otherNode, DynamicRelType.getType(type));
		rel.setProperty(DynamicTyper.PROTOTYPE_PROPERTY_NAME, type.getName());
		return DynamicTyper.wrapRelationship(rel);
	}

	//TODO Implement
	public Traverser traverse(Order traversalOrder,
			StopEvaluator stopEvaluator,
			ReturnableEvaluator returnableEvaluator,
			RelationshipType relationshipType, Direction direction) {
		return ((Node) this.pc).traverse(traversalOrder, stopEvaluator, returnableEvaluator, relationshipType, direction);
	}

	//TODO Implement
	public Traverser traverse(Order traversalOrder,
			StopEvaluator stopEvaluator,
			ReturnableEvaluator returnableEvaluator,
			RelationshipType firstRelationshipType, Direction firstDirection,
			RelationshipType secondRelationshipType, Direction secondDirection) {
		return ((Node) this.pc).traverse(traversalOrder, stopEvaluator, returnableEvaluator, 
				firstRelationshipType, firstDirection, secondRelationshipType, secondDirection);
	}

	//TODO Implement
	public Traverser traverse(Order traversalOrder,
			StopEvaluator stopEvaluator,
			ReturnableEvaluator returnableEvaluator,
			Object... relationshipTypesAndDirections) {
		return ((Node) this.pc).traverse(traversalOrder, stopEvaluator, returnableEvaluator, relationshipTypesAndDirections);
	}
	
	@Override
	public boolean equals(Object obj) {
		if (obj instanceof TypedNode) {
			return this.getId() == ((TypedNode) obj).getId();
		}
		return false;
	}

	public abstract void copyFrom(Object obj);

	public abstract void copyTo(Object obj);
}
